<?php

use Elliptic\EC;
use Ethereum\Eth;
use Ethereum\Formatter;
use Ethereum\HecoApi;
use Ethereum\HRC20;
use Ethereum\PEMHelper;
use Ethereum\Utils;
use Ethereum\TransactionEvent;
use kornrunner\Keccak;
use Web3p\EthereumTx\Transaction;

require_once WEB_PATH . "/../vendor/heco/autoload.php";

class Heco
{

    const HECO_KEY = '';
    const WALLET_PRIVATE_KEY = '';
    const WALLET_ADDRESS = '';

    const SUCCESS_TX_HASH = '';
    const FAIL_TX_HASH = '';

    public function hexToDec($hex){
        return Utils::hexToDec($hex);
    }
    /*
     *  获取火币余额
     * */
    public function balance($address)
    {

        $params = [$address, 'latest'];
        $res = $this->request("eth_getBalance", $params);
        if (!empty($res['result'])) {
            $balance = Utils::weiToEth(Utils::hexToDec($res['result']));
        }
        return $balance ?? 0;
    }

    /*
     *  获取代币精度
     *  @param $contract_address 代币合约地址
     * */
    public function contractDecimal($contract_address)
    {
        $cache_key = "contractDecimal_" . $contract_address;
        $decimals = \think\facade\Cache::get($cache_key);
        if ($decimals) {
            return $decimals;
        }
        $formatMethod = Formatter::toMethodFormat('decimals()');
        $data_arr = [
            'to' => $contract_address,
            'data' => "0x{$formatMethod}"
        ];
        $params = [$data_arr, 'latest'];
        $res = $this->request("eth_call", $params); //eth_call 用来调用合约函数
        if (!empty($res['result'])) {
            $decimals = Utils::hexToDec($res['result']);
            \think\facade\Cache::set($cache_key, $decimals);
        }
        return $decimals ?? 0;
    }

    /*
     *  查询交易状态
     *  @param $txid 交易哈希
     * */
    public function transactionReceipt($txid,$type = 1)
    {
        $params = [$txid];
        $res = $this->request("eth_getTransactionReceipt", $params); //eth_call 用来调用合约函数
        if($type == 1){
            $status = empty($res['result']['status'])?'':Utils::hexToDec($res['result']['status']);
            if($status === "1"){
                return 1;
            }elseif($status === "0"){
                return 0;
            }else{
                return -1;
            }
        }else{
            return $res['result'] ?? '-1';
        }
    }
    /*
     *  获取代币余额
     *  @param $contract_address 代币合约地址
     *  @param $address 要查询的钱包地址
     * */
    public function contractBalance($address, $contract_address)
    {
        $formatMethod = Formatter::toMethodFormat('balanceOf(address)');
        $formatAddress = Formatter::toAddressFormat($address);
        $data_arr = [
            'from' => $address,
            'to' => $contract_address,
            'data' => "0x{$formatMethod}{$formatAddress}"
        ];
        $params = [$data_arr, 'latest'];
        $res = $this->request("eth_call", $params); //eth_call 用来调用合约函数
        if (!empty($res['result'])) {
            $decimal = $this->contractDecimal($contract_address);
            $balance = bcdiv(Utils::hexToDec($res['result']),bcpow(10, $decimal), $decimal);
        }
        return number_to_normal($balance ?? 0,6);
    }


    /*
     *  代币转账
     *  @param $contractAddress 代币合约地址
     *  @param $from_address 转账钱包地址
     *  @param $privateKey 钱包密钥
     *  @param $transfer_num 转账数量
     * */
    public function contractTranfer($from_address,$transfer_num,$contractAddress,$privateKey,$to_adderss){

        $params = [
            'from' => $from_address,
            'to' => $contractAddress,
            'value' => '0x0',
        ];
        $decimal = $this->contractDecimal($contractAddress);
        $transfer_num = number_to_normal1($transfer_num);
        $formatMethod = Formatter::toMethodFormat('transfer(address,uint256)');
        $formatAddress = Formatter::toAddressFormat($to_adderss);
        $formatInteger = $this->formatMoney($transfer_num, $decimal);
        $params['data'] = "0x{$formatMethod}{$formatAddress}{$formatInteger}";
        $params['gas'] = $this->getGas($params);
        $params['nonce'] = $this->getNonce($from_address);
        $params['gasPrice'] = $this->getGasPrice();
        return $this->sendRawTransaction($params, $privateKey);
    }

    public function toAddressFormat($to_adderss){
        return Formatter::toAddressFormat($to_adderss);
    }

    /*
     *  为签名交易创建一个新的消息调用交易或合约。
     *  @param $data string 签名的交易数据
     * */
    public function sendRawTransaction($transaction,$privateKey){
        $chainId = 128;
        // 合并数据
        $transaction = array_merge([
            'nonce' => '01',
            'gasPrice' => '',
            'gas' => '',
            'to' => '',
            'value' => '',
            'data' => '',
            'chainId' => $chainId,
        ], $transaction);
        $transaction = new Transaction($transaction);
        $raw = $transaction->sign($privateKey);
        $res = $this->request("eth_sendRawTransaction", [Utils::add0x($raw)]);
        return $res['result'] ?? '';
    }

    /**
     * 对交易数据进行签名
     * @param $pri 十六进制私钥
     * @param $msg 十六进制数据
     * @return $signature
     */
    public function sign($data,$privateKey)
    {
        // sha1
        $hash = kornrunner\Keccak::hash(hex2bin($data), 256);

        $ec = new EC('secp256k1');
        // Generate keys
        $key = $ec->keyFromPrivate($privateKey);
        // Sign message (can be hex sequence or array)
        $signature = $key->sign($hash, ['canonical' => true]);

        // Verify signature
        return $signature;
    }
    /*
     *  移除0
     * */
    public function remove_fill0($str){

        $str = Utils::remove0x($str);
        while(1){
            $f_str = substr($str,0,1);
            if($f_str !== "0"){
                break;
            }
            $str = substr($str,1);
        }
        return $str;
    }

    /*
     *  formatMoney()
     * */
    public function formatMoney($money,$decimal){
		$money = number_to_normal($money*pow(10,$decimal),0);
        return Utils::fill0(Utils::remove0x(Utils::decToHex($money)));
//        return Utils::fill0(Utils::remove0x(Utils::decToHex(Utils::ethToWei($money))));

    }

    /*
    *  @api:http://cw.hubwiz.com/card/c/ethereum-json-rpc-api/1/3/6/
    *  当前的gas价格，单位：wei。
     * return 十六进制
    * */
    public function getGas($data){
        $params = [$data];
        $res = $this->request("eth_estimateGas", $params);
        if(!empty($res['result'])){
            $gas = Utils::hexToDec($res['result']) * 1.5;
        }
        return Utils::decToHex(($gas ?? 0));
    }

    /*
    *  @api:http://cw.hubwiz.com/card/c/ethereum-json-rpc-api/1/3/6/
    *  当前的gas价格，单位：wei。
     * return 十六进制
    * */
    public function getGasPrice(){
        $res = $this->request("eth_gasPrice");
        return $res['result'] ?? '';
    }

    public function getNonce($address){
        $params = [$address, 'latest'];
        $res = $this->request("eth_getTransactionCount", $params);
        return $res['result'] ?? '';
//        if (!empty($res['result'])) {
//            $nonce = Utils::hexToDec($res['result']);
//            $nonce = Utils::decToHex($nonce);
//        }
//        return $nonce ?? 0;
    }

    /*
     *  @api:http://cw.hubwiz.com/card/c/ethereum-json-rpc-api/1/3/10/
     * 指定地址发生的交易数量
     * */
    public function getNonce_old($address){
        $params = [$address, 'pending'];
        $res = $this->request("eth_getTransactionCount", $params);
        if (!empty($res['result'])) {
            $nonce = Utils::hexToDec($res['result']);
        }
        return $nonce ?? 0;
    }

    /*
     *  curl 请求
     * */
    private function request($methond, $params = [])
    {
        $api_url = "https://http-mainnet.hecochain.com";

        $send_data = [
            'jsonrpc' => '2.0',
            'method' => $methond,
            'id' => 67,
            'params' => $params
        ];
        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_URL => $api_url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => '',
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
//            CURLOPT_USERAGENT => "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20100101 Firefox/12.0",
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => 'POST',
            CURLOPT_POSTFIELDS => json_encode($send_data),
            CURLOPT_HTTPHEADER => array(
                'Content-Type: application/json'
            ),
        ));
        $response = curl_exec($curl);
        curl_close($curl);
        return json_decode($response, 1);
    }

    public function verify_sign(string $message, string $signature, string $address) : bool
    {
        $hash = Keccak::hash(sprintf("\x19Ethereum Signed Message:\n%s%s", strlen($message), $message), 256);
        $sign   = ['r' => substr($signature, 2, 64), 's' => substr($signature, 66, 64)];
        $recid  = ord(hex2bin(substr($signature, 130, 2))) - 27;
        if ($recid != ($recid & 1)) {
            return false;
        }
        $pubkey = (new EC('secp256k1'))->recoverPubKey($hash, $sign, $recid);
        $pubAddress = substr(Keccak::hash(substr(hex2bin($pubkey->encode('hex')), 1), 256), 24);
        return strtolower("0x".$pubAddress) === strtolower($address);
    }


}

?>